package com.yvan.dsf;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import com.fasterxml.jackson.datatype.joda.JodaModule;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.yvan.Conv;
import org.apache.commons.io.IOUtils;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;

import java.io.InputStream;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

/**
 * 分布式配置文件
 * Created by luoyifan on 2018/2/26.
 */
public class DsfConfigurableInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {

    private static final String DEFAULT_CONFIG_LOCATION = "classpath:dsf.yml";
    private static final String IMPORT_URL_KEY = "config-center-url";
    private static final String IMPORT_KEY = "config-import";
    private static final String IMPORT_LOGBACK = "config-logback";
    private static ObjectMapper objectYamlMapper;

    private final String[] args;
    private final String configLocation;
    private String importUrl;
    private final ResourceLoader resourceLoader;
    private final Map<String, Object> mapSource;
    private final List<String> logbackConfigs = Lists.newArrayList();

    static {
        objectYamlMapper = new ObjectMapper(new YAMLFactory());
        objectYamlMapper.registerModule(new JodaModule());
        objectYamlMapper.writerWithDefaultPrettyPrinter();
    }

    public DsfConfigurableInitializer(String[] args) {
        this(findInitArgs(args), args);
    }

    private static String findInitArgs(String[] args) {
        String r = DEFAULT_CONFIG_LOCATION;
        for (String l : args) {
            if (l.startsWith("--dsf-config=")) {
                r = l.substring("--dsf-config=".length());
                break;
            }
        }
        return r;
    }

    public DsfConfigurableInitializer(String configLocation, String[] args) {
        this.configLocation = configLocation;
        this.resourceLoader = new DefaultResourceLoader();
        this.args = args;

        mapSource = Maps.newLinkedHashMap();
        loadPropertySource(mapSource, configLocation);
    }

    public void loadPropertySource(Map<String, Object> map, String location) {
        Resource resource = resourceLoader.getResource(location);
        InputStream is = null;
        try {
            is = resource.getInputStream();
            JsonNode jsonNode = objectYamlMapper.readTree(is);
            flatMap(map, "", jsonNode);

        } catch (Exception e) {
            throw new RuntimeException(location + " load error!", e);

        } finally {
            IOUtils.closeQuietly(is);

        }
    }

    //添加 logback 的配置文件路径
    private void addLogbackConfig(String location) {
        if (!Strings.isNullOrEmpty(location)) {
            logbackConfigs.add(location);
        }
    }

    //生成并重新定义 logback
    private void configLogback() {
        try {
            boolean isDebug = Conv.NB(mapSource.get("debug"), false);
            LogBackConfigLoader.load(logbackConfigs, isDebug);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private void flatMap(Map<String, Object> map, String prefixField, JsonNode parent) {
        if (parent == null) {
            return;
        }
        String prefix = Strings.isNullOrEmpty(prefixField) ? prefixField : prefixField + ".";

        Iterator<String> fieldNames = parent.fieldNames();
        while (fieldNames.hasNext()) {
            String fieldName = fieldNames.next();
            JsonNode jn = parent.get(fieldName);

            if (fieldName.equals(IMPORT_KEY)) {
                //定义 import
                if (jn.isArray()) {
                    for (JsonNode importElement : jn) {
                        loadPropertySource(map, importUrl + importElement.asText());
                    }
                } else {
                    loadPropertySource(map, importUrl + jn.asText());
                }

            } else if (fieldName.equals(IMPORT_URL_KEY)) {
                //定义 import-url
                if (!Strings.isNullOrEmpty(importUrl)) {
                    throw new RuntimeException("import-url is duplication!");
                }
                importUrl = jn.asText();
                map.put(IMPORT_URL_KEY, importUrl);

            } else if (fieldName.equals(IMPORT_LOGBACK)) {
                //定义 import-logback-config
                if (jn.isArray()) {
                    for (JsonNode importElement : jn) {
                        addLogbackConfig(importUrl + importElement.asText());
                    }
                } else {
                    addLogbackConfig(importUrl + jn.asText());
                }

            } else {
                //正式的配置文件内容
                if (jn.isObject()) {
                    flatMap(map, prefix + fieldName, jn);

                } else if (jn.isArray()) {
                    ArrayNode arrayNode = (ArrayNode) jn;
                    int index = 0;
                    for (JsonNode element : arrayNode) {
                        if (element.isObject()) {
                            flatMap(map, prefix + fieldName + "[" + index + "]", element);

                        } else {
                            map.put(prefix + fieldName + "[" + index + "]", getJsonNodeValue(element));
                        }
                        index++;
                    }

                } else {
                    map.put(prefix + fieldName, getJsonNodeValue(jn));
                }
            }
        }
    }

    private Object getJsonNodeValue(JsonNode jn) {
        if (jn.isNull()) {
            return "";

        } else if (jn.isBoolean()) {
            return jn.asBoolean();

        } else if (jn.isDouble() || jn.isFloat()) {
            return jn.asDouble();

        } else if (jn.isInt() || jn.isShort()) {
            return jn.asInt();

        } else if (jn.isLong()) {
            return jn.asLong();

        } else if (jn.isTextual()) {
            return jn.asText();

        }
        throw new RuntimeException("unkown nodetype:" + jn);
    }

    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        MutablePropertySources propertySources = applicationContext.getEnvironment().getPropertySources();
        propertySources.addLast(new MapPropertySource(configLocation, mapSource));
        configLogback();
    }

    public String[] argsPlus() {
        //改变程序参数
        List<String> argList = Lists.newArrayList(args);

        /*
        String logginConfig = Conv.NS(mapSource.get("logging.config"));
        if (!Strings.isNullOrEmpty(logginConfig)) {
            //logback 配置文件没法用常规方法搞定
            String[] files = logginConfig.split("\\,");
            StringBuilder sb = new StringBuilder();
            String sp = "";
            for (String fileRaw : files) {
                String file = fileRaw.trim();
                if (Strings.isNullOrEmpty(file))
                    continue;
                if (!resourceLoader.getResource(file).exists()) {
                    //argList.add("--logging.config=" + importUrl + file);
                    sb.append(sp + importUrl + file);
                    sp = ",";
                } else {
                    //argList.add("--logging.config=" + file);
                    sb.append(sp + file);
                    sp = ",";
                }
            }
            argList.add("--logging.config=" + sb.toString());
        }
        */

        boolean debug = Conv.NB(mapSource.get("debug"));
        if (debug) {
            //debug 没法用常规方法搞定
            argList.add("--debug=" + debug);
        }

        String[] argsPlus = new String[argList.size()];
        argList.toArray(argsPlus);
        return argsPlus;
    }
}